using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using NFreeMarker.Ext.Util;
using NFreeMarker.Log;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Ext.Beans
{
    /**
     * Utility class that provides generic services to reflection classes.
     * It handles all polymorphism issues in the {@link #wrap(object)} and {@link #unwrap(TemplateModel)} methods.
     * @author Attila Szegedi
     * @version $Id: BeansWrapper.java,v 1.91.2.13 2007/04/02 13:08:59 szegedia Exp $
     */
    public class BeansWrapper : IObjectWrapper
    {
        public static readonly object CanNotUnwrap = new object();
       // private static readonly Type BIGINTEGER_CLASS = java.math.BigInteger.class;
        private static readonly Type BooleanClass = typeof (bool);
        private static readonly Type CharacterClass = typeof (char);
       // private static readonly Type COLLECTION_CLASS = Collection.class;
        private static readonly Type DateClass = typeof(DateTimeOffset);
        private static readonly Type HashadapterClass = typeof(HashAdapter);
       // private static readonly Type ITERABLE_CLASS;
       // private static readonly Type LIST_CLASS = List.class;
       // private static readonly Type MAP_CLASS = Map.class;
       // private static readonly Type NUMBER_CLASS = Number.class;
        private static readonly Type ObjectClass = typeof (object);
        private static readonly Type SequenceadapterClass = typeof (SequenceAdapter);
       // private static readonly Type SET_CLASS = Set.class;
        private static readonly Type SetadapterClass = typeof (SetAdapter);
        private static readonly Type StringClass = typeof (string);

        // static {
       //     Class iterable;
       //     try {
       //         iterable = Class.forName("java.lang.Iterable");
       //     }
       //     catch(ClassNotFoundException e) {
       //         // We're running on a pre-1.5 JRE
       //         iterable = null;
       //     }
       //     ITERABLE_CLASS = iterable;
       // }
        
        // When this property is true, some things are stricter. This is mostly to
        // catch anomalous things in development that can otherwise be valid situations
        // for our users.
        //private static readonly bool DEVELOPMENT = "true".Equals(SecurityUtilities.getSystemProperty("freemarker.development"));
        
        private static readonly Logger Logger = Logger.GetLogger("freemarker.beans");
        
        //private static readonly HashSet<MethodInfo> UnsafeMethods = CreateUnsafeMethodsSet();
        
        internal static readonly object GenericGetKey = new object();
        private static readonly object Constructors = new object();
        private static readonly object ArgTypes = new object();

        //private static readonly bool javaRebelAvailable = IsJavaRebelAvailable();
        
        /**
         * The default instance of BeansWrapper
         */
        private static readonly BeansWrapper Instance = new BeansWrapper();

        // Cache of hash maps that contain already discovered properties and methods
        // for a specified class. Each key is a Class, each value is a hash map. In
        // that hash map, each key is a property/method name, each value is a
        // MethodDescriptor or a PropertyDescriptor assigned to that property/method.
       private readonly Dictionary<Type, Dictionary<object, object>> _classCache = new Dictionary<Type, Dictionary<object, object>>();
       private HashSet<string> _cachedClassNames = new HashSet<string>();

        private readonly StaticModels _staticModels;
        private readonly ClassBasedModelFactory _enumModels;

       private readonly ModelCache _modelCache;
        
        private readonly BooleanModel _false;
        private readonly BooleanModel _true;

        /**
         * At this level of exposure, all methods and properties of the
         * wrapped objects are exposed to the template.
         */
        public const int ExposeAll = 0;
        
        /**
         * At this level of exposure, all methods and properties of the wrapped
         * objects are exposed to the template except methods that are deemed
         * not safe. The not safe methods are java.lang.object methods wait() and
         * notify(), java.lang.Class methods getClassLoader() and newInstance(),
         * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
         * newInstance() methods, all java.lang.reflect.Field set methods, all 
         * java.lang.Thread and java.lang.ThreadGroup methods that can change its 
         * state, as well as the usual suspects in java.lang.System and
         * java.lang.Runtime.
         */
        public const int ExposeSafe = 1;
        
        /**
         * At this level of exposure, only property getters are exposed.
         * Additionally, property getters that map to unsafe methods are not
         * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
         */
        public const int ExposePropertiesOnly = 2;

        /**
         * At this level of exposure, no bean properties and methods are exposed.
         * Only map items, resource bundle items, and objects retrieved through
         * the generic get method (on objects of classes that have a generic get
         * method) can be retrieved through the hash interface. You might want to 
         * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
         * speed up map item retrieval.
         */
        public const int ExposeNothing = 3;

       private int _exposureLevel = ExposeSafe;
       private ITemplateModel _nullModel;
       private bool _methodsShadowItems = true;
       private bool _exposeFields;
       private int _defaultDateType = TemplateDateModel.Unknown;

       private IObjectWrapper _outerIdentity;
       private bool _simpleMapWrapper;
       private bool _strict; 
        
        /**
         * Creates a new instance of BeansWrapper. The newly created instance
         * will use the null reference as its null object, it will use
         * {@link #ExposeSafe} method exposure level, and will not cache
         * model instances.
         */
        public BeansWrapper()
        {
            _enumModels = CreateEnumModels(this);
            _staticModels = new StaticModels(this);
            _true = new BooleanModel(true, this);
            _false = new BooleanModel(false, this);

            _booleanFactory = new BooleanModelFactory(this);

            _modelCache = new BeansModelCache(this);
            _outerIdentity = this;

            //if (javaRebelAvailable)
            //{
            //    JavaRebelIntegration.registerWrapper(this);
            //}
        }

        /**
        * @see #setStrict(boolean)
        */
        public bool IsStrict()
        {
            return _strict;
        }

        /**
         * Specifies if an attempt to read a bean property that doesn't exist in the
         * wrapped object should throw an {@link InvalidPropertyException}.
         * 
         * <p>If this property is <tt>false</tt> (the default) then an attempt to read
         * a missing bean property is the same as reading an existing bean property whose
         * value is <tt>null</tt>. The template can't tell the difference, and thus always
         * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
         * to handle the situation.
         *
         * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
         * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
         * object (as opposed to just holding <tt>null</tt> value) will cause
         * {@link InvalidPropertyException}, which can't be suppressed in the template
         * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
         * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
         * handle existing properties whose value is <tt>null</tt>, without the risk of
         * hiding typos in the property names. Typos will always cause error. But mind you, it
         * goes against the basic approach of FreeMarker, so use this feature only if you really
         * know what are you doing.
         */
        public void SetStrict(bool strict)
        {
            _strict = strict;
        }

        /**
         * When wrapping an object, the BeansWrapper commonly needs to wrap
         * "sub-objects", for example each element in a wrapped collection.
         * Normally it wraps these objects using itself. However, this makes
         * it difficult to delegate to a BeansWrapper as part of a custom
         * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
         * which will be used to wrap the sub-objects.
         * @param outerIdentity the aggregate ObjectWrapper
         */
        public void SetOuterIdentity(IObjectWrapper outerIdentity)
        {
            _outerIdentity = outerIdentity;
        }

        /**
         * By default returns <tt>this</tt>.
         * @see #setOuterIdentity(ObjectWrapper)
         */
        public IObjectWrapper GetOuterIdentity()
        {
            return _outerIdentity;
        }

        /**
         * By default the BeansWrapper wraps classes implementing
         * java.util.Map using {@link MapModel}. Setting this flag will
         * cause it to use a {@link SimpleMapModel} instead. The biggest
         * difference is that when using a {@link SimpleMapModel}, the
         * map will be visible as <code>TemplateHashModelEx</code>,
         * and the subvariables will be the content of the map,
         * without the other methods and properties of the map object.
         * @param simpleMapWrapper enable simple map wrapping
         */
        public void SetSimpleMapWrapper(bool simpleMapWrapper)
        {
            _simpleMapWrapper = simpleMapWrapper;
        }

        /**
         * Tells whether Maps are exposed as simple maps, without access to their
         * method. See {@link #setSimpleMapWrapper(boolean)} for details.
         * @return true if Maps are exposed as simple hashes, false if they're
         * exposed as full JavaBeans.
         */
        public bool IsSimpleMapWrapper()
        {
            return _simpleMapWrapper;
        }

        /**
         * Sets the method exposure level. By default, set to <code>ExposeSafe</code>.
         * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
         * constants.
         */
        public void SetExposureLevel(int exposureLevel)
        {
            if (exposureLevel < ExposeAll || exposureLevel > ExposeNothing)
            {
                throw new ArgumentException("Illegal exposure level " + exposureLevel);
            }
            _exposureLevel = exposureLevel;
        }

        internal int GetExposureLevel()
        {
            return _exposureLevel;
        }

        /**
         * Controls whether public instance fields of classes are exposed to 
         * templates.
         * @param exposeFields if set to true, public instance fields of classes 
         * that do not have a property getter defined can be accessed directly by
         * their name. If there is a property getter for a property of the same 
         * name as the field (i.e. getter "getFoo()" and field "foo"), then 
         * referring to "foo" in template invokes the getter. If set to false, no
         * access to public instance fields of classes is given. Default is false.
         */
        public void SetExposeFields(bool exposeFields)
        {
            _exposeFields = exposeFields;
        }

        /**
         * Returns whether exposure of public instance fields of classes is 
         * enabled. See {@link #setExposeFields(boolean)} for details.
         * @return true if public instance fields are exposed, false otherwise.
         */
        public bool IsExposeFields()
        {
            return _exposeFields;
        }
        
        /**
         * Sets whether methods shadow items in beans. When true (this is the
         * default value), <code>${object.name}</code> will first try to locate
         * a bean method or property with the specified name on the object, and
         * only if it doesn't find it will it try to call
         * <code>object.get(name)</code>, the so-called "generic get method" that
         * is usually used to access items of a container (i.e. elements of a map).
         * When set to false, the lookup order is reversed and generic get method
         * is called first, and only if it returns null is method lookup attempted.
         */
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetMethodsShadowItems(bool methodsShadowItems)
        {
            _methodsShadowItems = methodsShadowItems;
        }

        internal bool IsMethodsShadowItems()
        {
            return _methodsShadowItems;
        }
        
        /**
         * Sets the default date type to use for date models that result from
         * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
         * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is 
         * {@link TemplateDateModel#UNKNOWN}.
         * @param defaultDateType the new default date type.
         */
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void SetDefaultDateType(int defaultDateType)
        {
            _defaultDateType = defaultDateType;
        }

        /**
         * Returns the default date type. See {@link #setDefaultDateType(int)} for
         * details.
         * @return the default date type
         */
        internal int GetDefaultDateType()
        {
            return _defaultDateType;
        }

        /**
         * Sets whether this wrapper caches model instances. Default is false.
         * When set to true, calling {@link #wrap(object)} multiple times for
         * the same object will likely return the same model (although there is
         * no guarantee as the cache items can be cleared anytime).
         */
        public void SetUseCache(bool useCache)
        {
            _modelCache.SetUseCache(useCache);
        }

        /**
         * Sets the null model. This model is returned from the
         * {@link #wrap(object)} method whenever the underlying object 
         * reference is null. It defaults to null reference, which is dealt 
         * with quite strictly on engine level, however you can substitute an 
         * arbitrary (perhaps more lenient) model, such as 
         * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}.
         */
        public void SetNullModel(ITemplateModel nullModel)
        {
            _nullModel = nullModel;
        }
        
        /**
         * Returns the default instance of the wrapper. This instance is used
         * when you construct various bean models without explicitly specifying
         * a wrapper. It is also returned by 
         * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER}
         * and this is the sole instance that is used by the JSP adapter.
         * You can modify the properties of the default instance (caching,
         * exposure level, null model) to affect its operation. By default, the
         * default instance is not caching, uses the <code>ExposeSafe</code>
         * exposure level, and uses null reference as the null model.
         */
        public static BeansWrapper GetDefaultInstance()
        {
            return Instance;
        }

        /**
         * Wraps the object with a template model that is most specific for the object's
         * class. Specifically:
         * <ul>
         * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
         * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
         * <li>if the object is a Date returns a {@link DateModel} for it,</li>
         * <li>if the object is a Boolean returns 
         * {@link freemarker.template.TemplateBooleanModel#TRUE} or 
         * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
         * <li>if the object is already a TemplateModel, returns it unchanged,</li>
         * <li>if the object is an array, returns a {@link ArrayModel} for it
         * <li>if the object is a Map, returns a {@link MapModel} for it
         * <li>if the object is a Collection, returns a {@link CollectionModel} for it
         * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
         * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
         * <li>if the object is a string, returns a {@link StringModel} for it
         * <li>otherwise, returns a generic {@link BeanModel} for it.
         * </ul>
         */
        public virtual ITemplateModel Wrap(object obj)
        {
            return obj == null
                ? _nullModel
                : _modelCache.GetInstance(obj);
        }

         /**
         * @deprecated override {@link #getModelFactory(Class)} instead. Using this
         * method will now bypass wrapper caching (if it is enabled) and always 
         * result in creation of a new wrapper. This method will be removed in 2.4
         * @param object
         * @param factory
         */
        protected ITemplateModel GetInstance(object obj, IModelFactory factory)
        {
            return factory.Create(obj, this);
        }

        private readonly IModelFactory _booleanFactory;

        private class BooleanModelFactory : IModelFactory
        {
            private readonly BeansWrapper _beansWrapper;

            public BooleanModelFactory(BeansWrapper beansWrapper)
            {
                _beansWrapper = beansWrapper;
            }

            public ITemplateModel Create(object obj, IObjectWrapper wrapper)
            {
                return ((bool)obj) ? _beansWrapper._true : _beansWrapper._false; 
            }
        }

       // private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() {
       //     public TemplateModel create(object object, ObjectWrapper wrapper) {
       //         return new IteratorModel((Iterator)object, (BeansWrapper)wrapper); 
       //     }
       // };

       // private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() {
       //     public TemplateModel create(object object, ObjectWrapper wrapper) {
       //         return new EnumerationModel((Enumeration)object, (BeansWrapper)wrapper); 
       //     }
       // };

        protected internal IModelFactory GetModelFactory(Type type)
        {
            if (typeof (IDictionary).IsAssignableFrom(type))
            {
                return _simpleMapWrapper ? SimpleMapModel.Factory : MapModel.Factory;
            }
            if (typeof (ICollection).IsAssignableFrom(type))
            {
                return CollectionModel.Factory;
            }
       // TODO:    if(Number.class.isAssignableFrom(clazz)) {
       //         return NumberModel.FACTORY;
       //     }
            if (typeof (DateTimeOffset).IsAssignableFrom(type))
            {
                return DateModel.Factory;
            }
            if (typeof(bool) == type) // Boolean is final 
            {
                return _booleanFactory;
            }
       // TODO:    if(ResourceBundle.class.isAssignableFrom(clazz)) {
       //         return ResourceBundleModel.FACTORY;
       //     }
       // TODO:    if(Iterator.class.isAssignableFrom(clazz)) {
       //         return ITERATOR_FACTORY;
       //     }
       // TODO:    if(Enumeration.class.isAssignableFrom(clazz)) {
       //         return ENUMERATION_FACTORY;
       //     }
            if (type.IsArray)
            {
                return ArrayModel.Factory;
            }
            return StringModel.Factory;
        }

        /**
         * Attempts to unwrap a model into underlying object. Generally, this
         * method is the inverse of the {@link #wrap(object)} method. In addition
         * it will unwrap arbitrary {@link TemplateNumberModel} instances into
         * a number, arbitrary {@link TemplateDateModel} instances into a date,
         * {@link TemplateScalarModel} instances into a string, and
         * {@link TemplateBooleanModel} instances into a Boolean.
         * All other objects are returned unchanged.
         */
        public object Unwrap(ITemplateModel model)
        {
            return Unwrap(model, ObjectClass);
        }
        
        public object Unwrap(ITemplateModel model, Type hint)
        {
            return Unwrap(model, hint, null);
        }
        
        private object Unwrap(ITemplateModel model, Type hint, Dictionary<ITemplateModel, object> recursionStops) 
        {
            if (model == _nullModel)
            {
                return null;
            }
            
            bool isBoolean = typeof (bool) == hint;
            bool isChar = typeof (char) == hint;
            
            // This is for transparent interop with other wrappers (and ourselves)
            // Passing the hint allows i.e. a Jython-aware method that declares a
            // PyObject as its argument to receive a PyObject from a JythonModel
            // passed as an argument to TemplateMethodModelEx etc.
            if (model is IAdapterTemplateModel)
            {
                object adapted = ((IAdapterTemplateModel)model).GetAdaptedObject(hint);
                if (hint.IsInstanceOfType(adapted))
                {
                    return adapted;
                }
                // Attempt numeric conversion 
                //if (adapted is Number && ((hint.IsPrimitive && !isChar &&
                //        !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint)))
                //{
                //    Number number = ConvertUnwrappedNumber(hint, (Number)adapted);
                //    if (number != null)
                //    {
                //        return number;
                //    }
                //}
                throw new NotImplementedException();
            }
            
            if (model is IWrapperTemplateModel)
            {
                object wrapped = ((IWrapperTemplateModel)model).GetWrappedObject();
                if (hint.IsInstanceOfType(wrapped))
                {
                    return wrapped;
                }
                // Attempt numeric conversion 
                //if (wrapped is Number && ((hint.IsPrimitive && !isChar &&
                //        !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint)))
                //{
                //    Number number = ConvertUnwrappedNumber(hint,
                //            (Number)wrapped);
                //    if (number != null)
                //    {
                //        return number;
                //    }
                //}
                throw new NotImplementedException();
            }
            
            // Translation of generic template models to POJOs. First give priority
            // to various model interfaces based on the hint class. This helps us
            // select the appropriate interface in multi-interface models when we
            // know what is expected as the return type.

            if (StringClass == hint) {
                if (model is ITemplateScalarModel)
                {
                    return ((ITemplateScalarModel) model).GetAsString();
                }
                // string is final, so no other conversion will work
                return CanNotUnwrap;
            }

            // Primitive numeric types & Number.class and its subclasses
            // TODO: if ((hint.IsPrimitive && !isChar && !isBoolean)
            //   || NUMBER_CLASS.isAssignableFrom(hint))
            //{
            //    if (model is ITemplateNumberModel)
            //    {
            //        Number number = ConvertUnwrappedNumber(hint,
            //                ((ITemplateNumberModel) model).GetAsNumber());
            //        if (number != null)
            //        {
            //            return number;
            //        }
            //    }
            //}
            
            if (isBoolean || BooleanClass == hint)
            {
                if (model is ITemplateBooleanModel)
                {
                    return ((ITemplateBooleanModel) model).GetAsBoolean();
                }
                // Boolean is final, no other conversion will work
                return CanNotUnwrap;
            }

            // TODO: if (MAP_CLASS == hint)
            //{
            //    if (model is ITemplateHashModel)
            //    {
            //        return new HashAdapter((ITemplateHashModel)model, this);
            //    }
            //}

            // TODO: if (LIST_CLASS == hint)
            //{
            //    if (model is ITemplateSequenceModel)
            //    {
            //        return new SequenceAdapter((ITemplateSequenceModel) model, this);
            //    }
            //}

            // TODO: if (SET_CLASS == hint)
            //{
            //    if (model is ITemplateCollectionModel)
            //    {
            //        return new SetAdapter((ITemplateCollectionModel) model, this);
            //    }
            //}

            // TODO: if (COLLECTION_CLASS == hint || ITERABLE_CLASS == hint)
            //{
            //    if (model is ITemplateCollectionModel)
            //    {
            //        return new CollectionAdapter((ITemplateCollectionModel) model, this);
            //    }
            //    if (model is ITemplateSequenceModel)
            //    {
            //        return new SequenceAdapter((ITemplateSequenceModel) model, this);
            //    }
            //}
            
            // TemplateSequenceModels can be converted to arrays
            if (hint.IsArray)
            {
                if (model is ITemplateSequenceModel)
                {
                    //if (recursionStops != null)
                    //{
                    //    object retval = recursionStops.get(model);
                    //    if (retval != null)
                    //    {
                    //        return retval;
                    //    }
                    //}
                    //else
                    //{
                    //    recursionStops = new IdentityHashMap();
                    //}
                    //var seq = (ITemplateSequenceModel) model;
                    //Type componentType = hint.getComponentType();
                    //object array = Array.CreateInstance(componentType, seq.Size());
                    //recursionStops.put(model, array);
                    //try
                    //{
                    //    int size = seq.Size();
                    //    for (int i = 0; i < size; i++)
                    //    {
                    //        object val = Unwrap(seq.Get(i), componentType, 
                    //                recursionStops);
                    //        if (val == CAN_NOT_UNWRAP)
                    //        {
                    //            return CAN_NOT_UNWRAP;
                    //        }
                    //        Array.set(array, i, val);
                    //    }
                    //}
                    //finally
                    //{
                    //    recursionStops.remove(model);
                    //}
                    //return array;

                    throw new NotImplementedException();
                }
                // array classes are final, no other conversion will work
                return CanNotUnwrap;
            }
            
            // Allow one-char strings to be coerced to characters
            if (isChar || hint == CharacterClass)
            {
                if (model is ITemplateScalarModel) {
                    string s = ((ITemplateScalarModel) model).GetAsString();
                    if(s.Length == 1)
                    {
                        return s[0];
                    }
                }
                // Character is final, no other conversion will work
                return CanNotUnwrap;
            }

            if (DateClass.IsAssignableFrom(hint))
            {
                if (model is ITemplateDateModel)
                {
                    DateTimeOffset date = ((ITemplateDateModel) model).GetAsDate();
                    if (hint.IsInstanceOfType(date))
                    {
                        return date;
                    }
                }
            }
            
            // Translation of generic template models to POJOs. Since hint was of
            // no help initially, now use an admittedly arbitrary order of 
            // interfaces. Note we still test for isInstance and isAssignableFrom
            // to guarantee we return a compatible value. 
            if (model is ITemplateNumberModel)
            {
                Number number = ((ITemplateNumberModel) model).GetAsNumber();
                if (hint.IsInstanceOfType(number))
                {
                    return number;
                }
            }
            if (model is ITemplateDateModel)
            {
                DateTimeOffset date = ((ITemplateDateModel) model).GetAsDate();
                if (hint.IsInstanceOfType(date))
                {
                    return date;
                }
            }
            if (model is ITemplateScalarModel && hint.IsAssignableFrom(StringClass))
            {
                return ((ITemplateScalarModel) model).GetAsString();
            }
            if (model is ITemplateBooleanModel && hint.IsAssignableFrom(BooleanClass))
            {
                return ((ITemplateBooleanModel) model).GetAsBoolean();
            }
            if (model is ITemplateHashModel && hint.IsAssignableFrom(HashadapterClass))
            {
                return new HashAdapter((ITemplateHashModel) model, this);
            }
            if (model is ITemplateSequenceModel && hint.IsAssignableFrom(SequenceadapterClass))
            {
                return new SequenceAdapter((ITemplateSequenceModel) model, this);
            }
            if (model is ITemplateCollectionModel && hint.IsAssignableFrom(SetadapterClass))
            {
                return new SetAdapter((ITemplateCollectionModel) model, this);
            }

            // Last ditch effort - is maybe the model itself instance of the 
            // required type?
            if (hint.IsInstanceOfType(model))
            {
                return model;
            }
            
            return CanNotUnwrap;
        }

        private static Number ConvertUnwrappedNumber(Type hint, Number number)
        {
            //if (hint == Integer.TYPE || hint == Integer.class) {
            //    return number is Integer ? (Integer)number : 
            //        new Integer(number.intValue());
            //}
            //if(hint == Long.TYPE || hint == Long.class) {
            //    return number is Long ? (Long)number : 
            //        new Long(number.longValue());
            //}
            //if(hint == Float.TYPE || hint == Float.class) {
            //    return number is Float ? (Float)number : 
            //        new Float(number.floatValue());
            //}
            //if(hint == Double.TYPE 
            //        || hint == Double.class) {
            //    return number is Double ? (Double)number : 
            //        new Double(number.doubleValue());
            //}
            //if(hint == Byte.TYPE || hint == Byte.class) {
            //    return number is Byte ? (Byte)number : 
            //        new Byte(number.byteValue());
            //}
            //if(hint == Short.TYPE || hint == Short.class) {
            //    return number is Short ? (Short)number : 
            //        new Short(number.shortValue());
            //}
            //if(hint == BigInteger.class) {
            //    return number is BigInteger ? number : 
            //        new BigInteger(number.toString());
            //}
            //if(hint == BigDecimal.class) {
            //    if(number is BigDecimal) {
            //        return number;
            //    }
            //    if(number is BigInteger) {
            //        return new BigDecimal((BigInteger)number);
            //    }
            //    if(number is Long) {
            //        // Because we can't represent long accurately as a 
            //        // double
            //        return new BigDecimal(number.toString());
            //    }
            //    return new BigDecimal(number.doubleValue());
            //}
            //// Handle nonstandard Number subclasses as well as directly 
            //// java.lang.Number too
            //if(hint.isInstance(number)) {
            //    return number;
            //}
            //return null;

            throw new NotImplementedException();
        }
        
        /**
         * Invokes the specified method, wrapping the return value. The specialty
         * of this method is that if the return value is null, and the return type
         * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
         * @param object the object to invoke the method on
         * @param method the method to invoke 
         * @param args the arguments to the method
         * @return the wrapped return value of the method.
         * @throws InvocationTargetException if the invoked method threw an exception
         * @throws IllegalAccessException if the method can't be invoked due to an
         * access restriction. 
         * @throws TemplateModelException if the return value couldn't be wrapped
         * (this can happen if the wrapper has an outer identity or is subclassed,
         * and the outer identity or the subclass throws an exception. Plain
         * BeansWrapper never throws TemplateModelException).
         */
        internal ITemplateModel InvokeMethod(object obj, MethodInfo method, object[] args)
        {
            object retval = method.Invoke(obj, args);
            return 
                method.ReturnType == typeof (void)
                ? TemplateModel.Nothing
                : GetOuterIdentity().Wrap(retval); 
        }

       /**
         * Returns a hash model that represents the so-called class static models.
         * Every class static model is itself a hash through which you can call
         * static methods on the specified class. To obtain a static model for a
         * class, get the element of this hash with the fully qualified class name.
         * For example, if you place this hash model inside the root data model
         * under name "statics", you can use i.e. <code>statics["java.lang.
         * System"]. currentTimeMillis()</code> to call the {@link 
         * java.lang.System#currentTimeMillis()} method.
         * @return a hash model whose keys are fully qualified class names, and
         * that returns hash models whose elements are the static models of the
         * classes.
         */
        public ITemplateHashModel GetStaticModels()
        {
            return _staticModels;
        }
        
        /**
         * Returns a hash model that represents the so-called class enum models.
         * Every class' enum model is itself a hash through which you can access
         * enum value declared by the specified class, assuming that class is an
         * enumeration. To obtain an enum model for a class, get the element of this
         * hash with the fully qualified class name. For example, if you place this 
         * hash model inside the root data model under name "enums", you can use 
         * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the 
         * {@link java.math.RoundingMode#UP} value.
         * @return a hash model whose keys are fully qualified class names, and
         * that returns hash models whose elements are the enum models of the
         * classes.
         * @throws UnsupportedOperationException if this method is invoked on a 
         * pre-1.5 JRE, as Java enums aren't supported there.
         */
        public ITemplateHashModel GetEnumModels()
        {
            return _enumModels;
        }

        public object NewInstance(Type type, IList<ITemplateModel> arguments)
        {
            try
            {
                IntrospectClass(type);
                Dictionary<object, object> classInfo = _classCache[type];
                object ctors = classInfo.GetValueOrDefault<object, object>(Constructors);
                if (ctors == null)
                {
                    throw new TemplateModelException("Class " + type.FullName + 
                            " has no public constructors.");
                }
                ConstructorInfo ctor;
                object[] objargs;
                if(ctors is SimpleMemberModel)
                {
                    var smm = (SimpleMemberModel) ctors;
                    ctor = (ConstructorInfo) smm.GetMember();
                    objargs = smm.UnwrapArguments(arguments, this);
                }
                else if(ctors is MethodMap)
                {
                    var methodMap = (MethodMap) ctors; 
                    MemberAndArguments maa = 
                        methodMap.GetMemberAndArguments(arguments);
                    objargs = maa.GetArgs();
                    ctor = (ConstructorInfo) maa.GetMember();
                }
                else
                {
                    // Cannot happen
                    throw new Exception();
                }
                return ctor.Invoke(objargs);
            }
            catch (TemplateModelException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw new TemplateModelException(
                        "Could not create instance of class " + type.FullName, e);
            }
        }
        
        internal void IntrospectClass(Type type)
        {
            lock (_classCache)
            {
                if (!_classCache.ContainsKey(type))
                {
                    IntrospectClassInternal(type);
                }
            }
        }
        
        void RemoveIntrospectionInfo(Type type)
        {
            lock (_classCache)
            {
                _classCache.Remove(type);
                _staticModels.RemoveIntrospectionInfo(type);
                if (_enumModels != null)
                {
                    _enumModels.RemoveIntrospectionInfo(type);
                }
                _cachedClassNames.Remove(type.FullName);
                lock (this)
                {
                    _modelCache.ClearCache();
                }
            }
        }

        private void IntrospectClassInternal(Type type)
        {
            string className = type.FullName;
            if (_cachedClassNames.Contains(className))
            {
                if (Logger.IsInfoEnabled())
                {
                    Logger.Info("Detected a reloaded class [" + className +
                            "]. Clearing BeansWrapper caches.");
                }
                // Class reload detected, throw away caches
                _classCache.Clear();
                _cachedClassNames = new HashSet<string>();
                lock (this)
                {
                    _modelCache.ClearCache();
                }
                _staticModels.ClearCache();
                if (_enumModels != null)
                {
                    _enumModels.ClearCache();
                }
            }
            _classCache.Add(type, PopulateClassMap(type));
            _cachedClassNames.Add(className);
        }

        internal Dictionary<object, object> GetClassKeyMap(Type type)
        {
            Dictionary<object, object> map;
            lock (_classCache)
            {
                map = _classCache.GetValueOrDefault(type);
                if (map == null)
                {
                    IntrospectClassInternal(type);
                    map = _classCache.GetValueOrDefault(type);
                }
            }
            return map;
        }

        /**
         * Returns the number of introspected methods/properties that should
         * be available via the TemplateHashModel interface. Affected by the
         * {@link #setMethodsShadowItems(boolean)} and {@link
         * #setExposureLevel(int)} settings.
         */
        internal int KeyCount(Type type)
        {
            Dictionary<object, object> map = GetClassKeyMap(type);
            int count = map.Count;
            if (map.ContainsKey(Constructors))
                count--;
            if (map.ContainsKey(GenericGetKey))
                count--;
            if (map.ContainsKey(ArgTypes))
                count--;
            return count;
        }

        /**
         * Returns the Set of names of introspected methods/properties that
         * should be available via the TemplateHashModel interface. Affected
         * by the {@link #setMethodsShadowItems(boolean)} and {@link
         * #setExposureLevel(int)} settings.
         */
        internal HashSet<object> KeySet(Type type)
        {
            var set = new HashSet<object>(GetClassKeyMap(type).Keys);
            set.Remove(Constructors);
            set.Remove(GenericGetKey);
            set.Remove(ArgTypes);
            return set;
        }

        /**
         * Populates a map with property and method descriptors for a specified
         * class. If any property or method descriptors specifies a read method
         * that is not accessible, replaces it with appropriate accessible method
         * from a superclass or interface.
         */
        private Dictionary<object, object> PopulateClassMap(Type type)
        {
            // Populate first from bean info
            Dictionary<object, object> map = PopulateClassMapWithBeanInfo(type);
            // Next add constructors
            //try
            //{
                ConstructorInfo[] ctors = type.GetConstructors();
                if (ctors.Length == 1)
                {
                    ConstructorInfo ctor = ctors[0];
                    map.Add(Constructors, new SimpleMemberModel(ctor, ctor.GetParameters().Select(pi => pi.GetType()).ToArray()));
                }
                else if (ctors.Length > 1)
                {
                    var ctorMap = new MethodMap("<init>", this);
                    foreach (ConstructorInfo ctor in ctors)
                    {
                        ctorMap.AddMember(ctor);
                    }
                    map.Add(Constructors, ctorMap);
                }
            //}
            //catch (SecurityException e)
            //{
            //    logger.warn("Canont discover constructors for class " +
            //            clazz.getName(), e);
            //}
            //TODO> switch (map.Count)
            //{
            //    case 0:
            //        {
            //            map = Collections12.EMPTY_MAP;
            //            break;
            //        }
            //    case 1:
            //        {
            //            Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
            //            map = Collections12.singletonMap(e.getKey(), e.getValue());
            //            break;
            //        }
            //}
            return map;
        }

        private Dictionary<object, object> PopulateClassMapWithBeanInfo(Type type)
        {
            var classMap = new Dictionary<object, object>();
            if (_exposeFields)
            {
                FieldInfo[] fields = type.GetFields();
                foreach (FieldInfo field in fields)
                {
                    if (!field.IsStatic)
                    {
                        classMap.Add(field.Name, field);
                    }
                }
            }
            Dictionary<MethodSignature, ICollection<MethodInfo>> accessibleMethods = DiscoverAccessibleMethods(type);
            MethodInfo genericGet =
                GetFirstAccessibleMethod(MethodSignature.GetStringSignature, accessibleMethods) ??
                GetFirstAccessibleMethod(MethodSignature.GetObjectSignature, accessibleMethods);
            if (genericGet != null)
            {
                classMap.Add(GenericGetKey, genericGet);
            }
            if (_exposureLevel == ExposeNothing)
            {
                return classMap;
            }
            
            //try {
            //    BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            //    PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors();
            //    MethodDescriptor[] mda = beanInfo.getMethodDescriptors();

            PropertyInfo[] pda = type.GetProperties();
            MethodInfo[] mda = type.GetMethods();

                for(int i = pda.Length - 1; i >= 0; --i)
                {
                    PropertyInfo pd = pda[i];
                    // TODO: if (pd is IndexedPropertyDescriptor)
                    //{
                    //    IndexedPropertyDescriptor ipd = 
                    //        (IndexedPropertyDescriptor)pd;
                    //    Method readMethod = ipd.getIndexedReadMethod();
                    //    Method publicReadMethod = getAccessibleMethod(readMethod, 
                    //            accessibleMethods);
                    //    if(publicReadMethod != null && isSafeMethod(publicReadMethod)) {
                    //        try {
                    //            if(readMethod != publicReadMethod) {
                    //                ipd = new IndexedPropertyDescriptor(
                    //                        ipd.getName(), ipd.getReadMethod(), 
                    //                        ipd.getWriteMethod(), publicReadMethod, 
                    //                        ipd.getIndexedWriteMethod());
                    //            }
                    //            classMap.put(ipd.getName(), ipd);
                    //            getArgTypes(classMap).put(publicReadMethod, 
                    //                    publicReadMethod.getParameterTypes());
                    //        }
                    //        catch(IntrospectionException e) {
                    //            logger.warn("Failed creating a publicly-accessible " +
                    //                    "property descriptor for " + clazz.getName() + 
                    //                    " indexed property " + pd.getName() + 
                    //                    ", read method " + publicReadMethod + 
                    //                    ", write method " + ipd.getIndexedWriteMethod(), 
                    //                    e);
                    //        }
                    //    }
                    //}
                    //else {
                        MethodInfo readMethod = pd.GetGetMethod();
                        MethodInfo publicReadMethod = GetAccessibleMethod(readMethod, accessibleMethods);
                        if (publicReadMethod != null && IsSafeMethod(publicReadMethod))
                        {
                            //try
                            //{
                                if (readMethod != publicReadMethod)
                                {
                                    //pd = new PropertyDescriptor(pd.getName(), 
                                    //        publicReadMethod, pd.getWriteMethod());
                                    //pd.setReadMethod(publicReadMethod);
                                    throw new NotImplementedException();
                                }
                                classMap.Add(pd.Name, pd);
                            //}
                            //catch(IntrospectionException e) {
                            //    logger.warn("Failed creating a publicly-accessible " +
                            //            "property descriptor for " + clazz.getName() + 
                            //            " property " + pd.getName() + ", read method " + 
                            //            publicReadMethod + ", write method " + 
                            //            pd.getWriteMethod(), e);
                            //}
                        }
                    }
                //}
                if (_exposureLevel < ExposePropertiesOnly)
                {
                    for (int i = mda.Length - 1; i >= 0; --i)
                    {
                        MethodInfo method = mda[i];
                        MethodInfo publicMethod = GetAccessibleMethod(method, accessibleMethods);
                        if (publicMethod != null && IsSafeMethod(publicMethod))
                        {
                            string name = method.Name;
                            object previous = classMap.GetValueOrDefault<object, object>(name);
                            if (previous is MethodInfo)
                            {
                                // Overloaded method - replace method with a method map
                                var methodMap = new MethodMap(name, this);
                                methodMap.AddMember((MethodInfo) previous);
                                methodMap.AddMember(publicMethod);
                                classMap[name] = methodMap;
                                // remove parameter type information
                                GetArgTypes(classMap).Remove(previous);
                            }
                            else if (previous is MethodMap)
                            {
                                // Already overloaded method - add new overload
                                ((MethodMap) previous).AddMember(publicMethod);
                            }
                            else
                            {
                                // Simple method (this far)
                                classMap.Add(name, publicMethod);
                                GetArgTypes(classMap).Add(
                                    publicMethod, 
                                    publicMethod.GetParameters().Select(pi => pi.GetType()).ToArray());
                            }
                        }
                    }
                }
                return classMap;
       //     }
       //     catch(IntrospectionException e) {
       //         logger.warn("Couldn't properly perform introspection for class " + 
       //                 clazz, e);
       //         return new HashMap();
       //     }
        }

        private static Dictionary<object, Type[]> GetArgTypes(Dictionary<object, object> classMap)
        {
            var argTypes = (Dictionary<object, Type[]>) classMap.GetValueOrDefault<object, object>(ArgTypes);
            if (argTypes == null)
            {
                argTypes = new Dictionary<object, Type[]>();
                classMap.Add(ArgTypes, argTypes);
            }
            return argTypes;
        }

        internal static Type[] GetArgTypes(Dictionary<object, object> classMap, MemberInfo methodOrCtor)
        {
            return ((Dictionary<object, Type[]>)classMap[ArgTypes])[methodOrCtor];
        }

        private static MethodInfo GetFirstAccessibleMethod(MethodSignature sig, Dictionary<MethodSignature, ICollection<MethodInfo>> accessibles)
        {
            ICollection<MethodInfo> l = accessibles.GetValueOrDefault(sig);
            if (l == null)
            {
                return null;
            }

            IEnumerator<MethodInfo> e = l.GetEnumerator();
            return e.MoveNext() ? e.Current : null;
        }

        private static MethodInfo GetAccessibleMethod(MethodInfo m, Dictionary<MethodSignature, ICollection<MethodInfo>> accessibles)
        {
            if (m == null)
            {
                return null;
            }
            var sig = new MethodSignature(m);
            ICollection<MethodInfo> l = accessibles.GetValueOrDefault(sig);
            if (l == null)
            {
                return null;
            }
            return l.FirstOrDefault(am => am.ReturnType == m.ReturnType);
        }

        internal bool IsSafeMethod(MethodInfo method)
        {
            return true; // TODO: exposureLevel < ExposeSafe || !UNSAFE_METHODS.contains(method);
        }

        /**
         * Retrieves mapping of methods to accessible methods for a class.
         * In case the class is not public, retrieves methods with same 
         * signature as its public methods from public superclasses and 
         * interfaces (if they exist). Basically upcasts every method to the 
         * nearest accessible method.
         */
        private static Dictionary<MethodSignature, ICollection<MethodInfo>> DiscoverAccessibleMethods(Type type)
        {
            var map = new Dictionary<MethodSignature, ICollection<MethodInfo>>();
            DiscoverAccessibleMethods(type, map);
            return map;
        }

        private static void DiscoverAccessibleMethods(Type type, Dictionary<MethodSignature, ICollection<MethodInfo>> map)
        {
            if (type.IsPublic || type.IsNestedPublic)
            {
                //try
                //{
                    MethodInfo[] methods = type.GetMethods();
                    foreach (MethodInfo method in methods)
                    {
                        var sig = new MethodSignature(method);
                        // Contrary to intuition, a class can actually have several 
                        // different methods with same signature *but* different
                        // return types. These can't be constructed using Java the
                        // language, as this is illegal on source code level, but 
                        // the compiler can emit synthetic methods as part of 
                        // generic type reification that will have same signature 
                        // yet different return type than an existing explicitly
                        // declared method. Consider:
                        // public interface I<T> { T m(); }
                        // public class C implements I<Integer> { Integer m() { return 42; } }
                        // C.class will have both "object m()" and "Integer m()" methods.
                        ICollection<MethodInfo> methodList = map.GetValueOrDefault(sig);
                        if (methodList == null)
                        {
                            methodList = new LinkedList<MethodInfo>();
                            map.Add(sig, methodList);
                        }
                        methodList.Add(method);
                    }
                    return;
                //}
                // TODO catch (SecurityException e)
                //{
                //    logger.warn("Could not discover accessible methods of class " +
                //            clazz.getName() +
                //            ", attemping superclasses/interfaces.", e);
                //    // Fall through and attempt to discover superclass/interface 
                //    // methods
                //}
            }

            Type[] interfaces = type.GetInterfaces();
            foreach (Type t in interfaces)
            {
                DiscoverAccessibleMethods(t, map);
            }
            Type superclass = type.BaseType;
            if (superclass != null)
            {
                DiscoverAccessibleMethods(superclass, map);
            }
        }

        private class MethodSignature
        {
            internal static readonly MethodSignature GetStringSignature = 
                new MethodSignature("get", new [] { StringClass });

            internal static readonly MethodSignature GetObjectSignature = 
                new MethodSignature("get", new [] { ObjectClass });

            private readonly string _name;
            private readonly Type[] _args;
            
            private MethodSignature(string name, Type[] args)
            {
                _name = name;
                _args = args;
            }
            
            internal MethodSignature(MethodInfo method)
                : this(method.Name, method.GetParameterTypes())
            {
            }
            
            public override bool Equals(object o)
            {
                if (o is MethodSignature)
                {
                    var ms = (MethodSignature) o;
                    return ms._name.Equals(_name) && _args.SequenceEqual(ms._args);
                }
                return false;
            }
            
            public override int GetHashCode()
            {
                return _name.GetHashCode() ^ _args.Length;
            }
        }
        
        private static HashSet<MethodInfo> CreateUnsafeMethodsSet()
        {
            //Properties props = new Properties();
            //InputStream input = BeansWrapper.class.getResourceAsStream("unsafeMethods.txt");
            //if (input != null)
            //{
            //    string methodSpec = null;
            //    try
            //    {
            //        try
            //        {
            //            props.load(input);
            //        }
            //        finally
            //        {
            //            input.close();
            //        }
            //        var set = new HashSet<MethodInfo>(props.size() * 4/3, .75f);
            //        Dictionary<string, Type> primClasses = CreatePrimitiveClassesMap();
            //        for (Iterator iterator = props.keySet().iterator(); iterator.hasNext();)
            //        {
            //            methodSpec = (string) iterator.next();
            //            try
            //            {
            //                set.Add(ParseMethodSpec(methodSpec, primClasses));
            //            }
            //            catch (ClassNotFoundException e)
            //            {
            //                if (DEVELOPMENT)
            //                {
            //                    throw e;
            //                }
            //            }
            //            catch (NoSuchMethodException e)
            //            {
            //                if (DEVELOPMENT)
            //                {
            //                    throw e;
            //                }
            //            }
            //        }
            //        return set;
            //    }
            //    catch(Exception e)
            //    {
            //        throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.GetType().FullName + " " + e.Message);
            //    }
            //}
            //return Collections.EMPTY_SET;

            throw new NotImplementedException();
        }
                                                                               
        private static MethodInfo ParseMethodSpec(string methodSpec, Dictionary<string, Type> primClasses)
        {
            int brace = methodSpec.IndexOf('(');
            int dot = methodSpec.LastIndexOf('.', brace);
            Type type = ClassUtil.ForName(methodSpec.Substring(0, dot));
            string methodName = methodSpec.Substring(dot + 1, brace - dot - 1);
            string argSpec = methodSpec.Substring(brace + 1, methodSpec.Length - brace - 2);
            string[] tok = argSpec.Split(',');
            int argcount = tok.Length;
            var argTypes = new Type[argcount];
            for (int i = 0; i < argcount; i++)
            {
                string argClassName = tok[i];
                argTypes[i] = primClasses.GetValueOrDefault(argClassName);
                if(argTypes[i] == null)
                {
                    argTypes[i] = ClassUtil.ForName(argClassName);
                }
            }
            return type.GetMethod(methodName, argTypes);
        }

        private static Dictionary<string, Type> CreatePrimitiveClassesMap()
        {
            return new Dictionary<string, Type>
            {
                {"boolean", typeof (bool)},
                {"byte", typeof (byte)},
                {"char", typeof (char)},
                {"short", typeof (short)},
                {"int", typeof (int)},
                {"long", typeof (long)},
                {"float", typeof (float)},
                {"double", typeof (double)}
            };
        }

        /**
         * Converts any {@link BigDecimal}s in the passed array to the type of
         * the corresponding formal argument of the method.
         */
        //public static void CoerceBigDecimals(AccessibleObject callable, object[] args)
        //{
        //    Type[] formalTypes = null;
        //    for (int i = 0; i < args.Length; ++i)
        //    {
        //        object arg = args[i];
        //        if (arg is BigDecimal)
        //        {
        //            if (formalTypes == null)
        //            {
        //                if (callable is MethodInfo || callable is ConstructorInfo)
        //                {
        //                    formalTypes = ((MethodBase) callable).GetParameterTypes();
        //                }
        //                else
        //                {
        //                    throw new ArgumentException("Expected method or "
        //                            + " constructor; callable is " + 
        //                            callable.GetType().FullName);
        //                }
        //            }
        //            args[i] = CoerceBigDecimal((BigDecimal) arg, formalTypes[i]);
        //        }
        //    }
        //}

        /**
         * Converts any {@link BigDecimal}s in the passed array to the type of
         * the corresponding formal argument of the method.
         */
        public static void CoerceBigDecimals(Type[] formalTypes, object[] args)
        {
            int typeLen = formalTypes.Length;
            int argsLen = args.Length;
            int min = Math.Min(typeLen, argsLen);
            for (int i = 0; i < min; ++i)
            {
                //object arg = args[i];
                //if (arg is BigDecimal)
                //{
                //    args[i] = CoerceBigDecimal((BigDecimal)arg, formalTypes[i]);
                //}

                throw new NotImplementedException();
            }
            if (argsLen > typeLen)
            {
                Type varArgType = formalTypes[typeLen - 1];
                for (int i = typeLen; i < argsLen; ++i)
                {
                    //object arg = args[i];
                    //if (arg is BigDecimal)
                    //{
                    //    args[i] = CoerceBigDecimal((BigDecimal)arg, varArgType);
                    //}

                    throw new NotImplementedException();
                }
            }
        }
        
       // public static object coerceBigDecimal(BigDecimal bd, Class formalType) {
       //     // int is expected in most situations, so we check it first
       //     if(formalType == Integer.TYPE || formalType == Integer.class) {
       //         return new Integer(bd.intValue());
       //     }
       //     else if(formalType == Double.TYPE || formalType == Double.class) {
       //         return new Double(bd.doubleValue());
       //     }
       //     else if(formalType == Long.TYPE || formalType == Long.class) {
       //         return new Long(bd.longValue());
       //     }
       //     else if(formalType == Float.TYPE || formalType == Float.class) {
       //         return new Float(bd.floatValue());
       //     }
       //     else if(formalType == Short.TYPE || formalType == Short.class) {
       //         return new Short(bd.shortValue());
       //     }
       //     else if(formalType == Byte.TYPE || formalType == Byte.class) {
       //         return new Byte(bd.byteValue());
       //     }
       //     else if(BIGINTEGER_CLASS.isAssignableFrom(formalType)) {
       //         return bd.toBigInteger();
       //     }
       //     return bd;
       // }

        private static ClassBasedModelFactory CreateEnumModels(BeansWrapper wrapper)
        {
            return new EnumModels(wrapper);
        }
        
        //private static bool IsJavaRebelAvailable()
        //{
        //    try
        //    {
        //        JavaRebelIntegration.TestAvailability();
        //        return true;
        //    }
        //    catch (NoClassDefFoundError)
        //    {
        //        return false;
        //    }
        //}
    }
}